package vek.ObjectDatabase;

import java.awt.Point;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import vek.Objects.POI;

/**
 * A very simple database based on objects which contains POIs.
 * This database can search through the POIs based on a search objects which contains the search arguments.
 * This database is based on calling methods in the POIs and returning a list of POIs that match the search arguments.
 * @author Christian, Michal og Jakob (Hold 1, gruppe 2)
 */
public class SimpleDatabase implements POIDatabase{
    
    private ArrayList<POI> allPOIObjects;
    
    private ArrayList<String> allTypes;
    private DatabaseConnector.DatabaseType type;
    private boolean isOnline;
    private int priority;
    
    /**
     * Creates the SimpleDatabase and fills the allTypes arraylist with the types of POIs (please not that this is just the various types, not the actual POI objects)
     * @param t the type of database
     * @param isO determines if the database is online (based on the internet) or offline. In the current implementation, the database is always offline.
     * @param prio the priority of the database, used to determine which database the DatabaseConnector selects to use
     * @param pois the POIs which this database contains
     */
    
    public SimpleDatabase(DatabaseConnector.DatabaseType t, boolean isO, int prio, ArrayList<POI> pois)
    {
        type = t;
        isOnline = isO;
        priority = prio;
        
        
        allPOIObjects = pois;
        
        loadAllTypes();
        
        assert (allPOIObjects != null && priority > 0) : "error creating database"; // makes sure that the priority is not negative and that there are actually POIs in this database
    }
    
    /**
     * Gets a list of all the different types of POI. Each POI has a type, 
     * for example "Restaurant" and "Museum". This is used to display a list of types in the GUI
     * 
     * NOTE: This method will only return one of each type, so that if 2 POIs have the type "Restaurant", 
     * it will only be added to the list once
     */
    
    private void loadAllTypes()
    {
        allTypes = new ArrayList<String>();
        
        for(int i = 0; i < allPOIObjects.size(); i++)
        {
            if( allTypes.contains( allPOIObjects.get(i).getType() ) == false )
            {
                allTypes.add( allPOIObjects.get(i).getType() );
            }
        }
    }
    
    /**
     * Gets the types, see the loadAllTypes() method
     * @return an ArrayList containing all the types
     */
    
    public ArrayList<String> getAllTypes()
    {
        return allTypes;
    }
    
    /**
     * This method searches through the POIs and returns the ones that match the search arguments in the search object.
     * The search uses the various private methods depending on the search type, 
     * so that the search type NAMESTARTSWITH will use the searchForNameStartsWith method.
     * @param se the search object which contains the search arguments which determine which kind of POIs should be returned
     * @return the POIs that match the search arguments contained in the search object
     */
    
    public ArrayList<POI> searchFor(Search se)
    {
        if(se.getSearcTypes().size() == 0)
        {
            return new ArrayList<POI>();
        }
        ArrayList<POI> listOfPOIs = allPOIObjects;
        for(int i = 0; i < se.getSearcTypes().size(); i++)
        {
            String arg = se.getSearchArguments().get(i)[0];
            
            if(se.getSearcTypes().get(i).equals(Search.SearchType.NAMESTARTSWITH))
            {
                listOfPOIs = searchForNameStartsWith(arg, listOfPOIs);
            }
            else if(se.getSearcTypes().get(i).equals(Search.SearchType.TYPESTARTSWITH))
            {
                listOfPOIs = searchForTypeStartsWith(arg, listOfPOIs);
            }
            else if(se.getSearcTypes().get(i).equals(Search.SearchType.PHONENUMBERSTARTSWITH))
            {
                listOfPOIs = searchForPhoneNumberStartsWith(arg, listOfPOIs);
            }
            else if(se.getSearcTypes().get(i).equals(Search.SearchType.ROADSTARTSWITH))
            {
                listOfPOIs = searchForRoadStartsWith(arg, listOfPOIs);
            }
            else if(se.getSearcTypes().get(i).equals(Search.SearchType.HASTOILETS))
            {
                listOfPOIs = searchForHasToilets(listOfPOIs);
            }
            else if(se.getSearcTypes().get(i).equals(Search.SearchType.HASELEVATORS))
            {
                listOfPOIs = searchForHasElevators(listOfPOIs);
            }
            else if(se.getSearcTypes().get(i).equals(Search.SearchType.ISHANDICAPFRIENDLY))
            {
                listOfPOIs = searchForIsHandicapFriendly(listOfPOIs);
            }
            else if(se.getSearcTypes().get(i).equals(Search.SearchType.ISOPEN))
            {
                assert false : "search type isopen is not set";
            }
            else if(se.getSearcTypes().get(i).equals(Search.SearchType.ISWITHXMETERS))
            {
                String[] args =  se.getSearchArguments().get(i);
                Ellipse2D.Double e2d = new Ellipse2D.Double(Double.valueOf(args[0]), 
                        Double.valueOf(args[1]),Double.valueOf(args[2]), Double.valueOf(args[3]));
                //System.out.println("x: " + args[0] + " y: " + args[1]);
                //System.out.println(e2d.getX() + ", " + e2d.getY());
                listOfPOIs = searchForLocationIsWithinCircle(e2d, listOfPOIs);
            }
            else if(se.getSearcTypes().get(i).equals(Search.SearchType.TYPEEQUALS))
            {
                listOfPOIs = searchForTypeEquals(arg, listOfPOIs);
            }
        }
        return listOfPOIs;
    }
    
    /**
     * adds a POI to this database
     * @param poiObj the POI to be added
     */
    
    public void addPOI(POI poiObj)
    {
        allPOIObjects.add(poiObj);
    }
    
    /**
     * gets all the POIs in this database
     * @return all the POIs in this database
     */
    
    public ArrayList<POI> getAll()
    {
        return allPOIObjects;
    }
    
    /**
     * searches for all the POIs where the name starts a string
     * @param s the string which the returned POIs start with
     * @param listSearchFrom the list of POIs to search
     * @return all the POIs where the name starts with the given string
     */

    private ArrayList<POI> searchForNameStartsWith(String s, ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if(listSearchFrom.get(i).getName().toLowerCase().startsWith(s.toLowerCase()))
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Gets all the POIs in this database where the type equals the one given
     * <p>
     * NOTE: this is NOT case sensitive
     * 
     * @param s the type that the POIs should equal
     * @param listSearchFrom the POIs to search
     * @return the POIs where the type equals the one given in the paramter string
     */

    private ArrayList<POI> searchForgetTypeEquals(String s, ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if(listSearchFrom.get(i).getType().equalsIgnoreCase(s))
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Gets all the POIs where the type starts witb the given string
     * <p>
     * NOTE: this is NOT case sensitive
     * 
     * @param s the string which the returned POIs must start with
     * @param listSearchFrom the POIs to search
     * @return the POIs whgere the type starts with the given string
     */

    private ArrayList<POI> searchForTypeStartsWith(String s, ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if(listSearchFrom.get(i).getType().toLowerCase().startsWith(s.toLowerCase()))
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }

    /**
     * Gets the POIs where the location is within the specified circle
     * <p>
     * NOTE: the location is based on pixels, so that a given POIs location is also its X and Y coordinates on the map
     * 
     * @param e2d the circle which the POIs location must be within
     * @param listSearchFrom the POIs to search
     * @return the POIs which location are within the circle specified
     */

    private ArrayList<POI> searchForLocationIsWithinCircle(Ellipse2D e2d, ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if( e2d.contains( listSearchFrom.get(i).getLocation() ))
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Gets the POIs where the location is exactly equal to the location given.
     * uses the equals() method in the Point class to compare to points.
     * @see Point
     * <p>
     * NOTE: the location is based on pixels, so that a given POIs location is also its X and Y coordinates on the map
     * 
     * @param p the location which the POIs must equal
     * @param listSearchFrom the POIs to search
     * @return the POIs where the location equals the one specified in the parameter
     */

    private ArrayList<POI> searchForLocationEquals(Point p, ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if(listSearchFrom.get(i).getLocation().equals(p))
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Gets the POIs where the types of exhibitions start with the specified string
     * @param s the string which the POIs should starts with
     * @param listSearchFrom the POIs to search
     * @return the POIs where the types of exhibitions starts with the specified string
     */

    private ArrayList<POI> searchForTypesOfExhibtionsStartWith(String s, ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if(listSearchFrom.get(i).hasTypesOfExhibitionsStartsWith(s))
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Gets the POIs where the road starts with the specified string.
     * <p>
     * NOTE: this method is NOT case sensitive
     * 
     * @param s the string which 
     * @param listSearchFrom the POIs to search
     * @return the POIs where the road starts with the specified string
     */  

    private ArrayList<POI> searchForRoadStartsWith(String s, ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if(listSearchFrom.get(i).getAddresse().roadStartsWith(s.toLowerCase()))
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Gets the POIs where the type equals the type given.
     * <p>
     * NOTE: this method is NOT case sensitive
     * 
     * @param type the type which the POI must have
     * @param listSearchFrom the POIs to search
     * @return the POIs where the type equals the one specified
     */
    private ArrayList<POI> searchForTypeEquals(String type, ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if(listSearchFrom.get(i).typeEquals(type))
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Gets the POIs which are open at the specified time
     * @param day the day which the POI must be open
     * @param hour the hour which the POI must be open
     * @param minute the minute where the POI must be open
     * @param listSearchFrom the POIs to search
     * @return the POIs which are open at the specified time
     */

    private ArrayList<POI> searchForIsOpen(int day, int hour, int minute, ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if(listSearchFrom.get(i).isOpen(day, hour, minute))
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Gets the POIs which have toilets, that is where the hasToilets method returns true
     * @param listSearchFrom the POIs to search
     * @return the POIs which have toilets
     */

    private ArrayList<POI> searchForHasToilets(ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if( listSearchFrom.get(i).hasToilets() == 1)
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Gets the POIs which have elevators, that is POIs where the hasElevators() method returns true.
     * @param listSearchFrom the POIs to search
     * @return the POIs which have elevators
     */

    private ArrayList<POI> searchForHasElevators(ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if( listSearchFrom.get(i).hasElevators() == 1)
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Gets the POIs which are handicap friendly, that is where the isHandicapFriendly() method returns true.
     * @param listSearchFrom the POIs to search
     * @return the POIs which are handicap friendly
     */

    private ArrayList<POI> searchForIsHandicapFriendly(ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if( listSearchFrom.get(i).isHandicapFriendly() == 1)
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Gets the POIs where the phone number starts with the specified string
     * <p>
     * NOTE: This method compares phone numbers as Strings, not ints or doubles, thus non-numbers can also be used.
     *       can cauase unforseen problems, but is also a bit less exception prone compared to using doubles.
     * 
     * @param s the string which contains the phone number
     * @param listSearchFrom the POIs to search
     * @return the POIs where the phone number starts with the specified string
     */

    private ArrayList<POI> searchForPhoneNumberStartsWith(String s, ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if(listSearchFrom.get(i).phonenumberStartsWith(s))
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Gets the POIs where the types of products sold starts with the specified string
     * @param s the string that specifies what the products must start with
     * @param listSearchFrom the POIs to search
     * @return the POIs where the types of products sold starts with the specified string
     */

    private ArrayList<POI> searchForHasTypesOfProductsSoldStartsWith(String s, ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if(listSearchFrom.get(i).hasTypesOfFoodStartsWith(s))
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Gets the POIs which has at least one product which name starts with the specified string
     * @param s the string which contains the product name
     * @param listSearchFrom the POIs to search
     * @return The POIs where at least one product name starts with the specifies string
     */

    private ArrayList<POI> searchForHasProductNameStartsWith(String s, ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if(listSearchFrom.get(i).hasProductNameWhichStartsWith(s))
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Gets all the POIs where the types of food sold starts with the specified string
     * @param s the type of food sold
     * @param listSearchFrom the POIs to search
     * @return The POIs where at least one type of food starts with the specified string
     */
    
    private ArrayList<POI> searchForHasTypesOfFoodStartsWith(String s, ArrayList<POI> listSearchFrom)
    {
        ArrayList<POI> results = new ArrayList<POI>();
        for(int i = 0; i < listSearchFrom.size(); i++)
        {
            if(listSearchFrom.get(i).hasTypesOfFoodStartsWith(s))
            {
                results.add( listSearchFrom.get(i) );
            }
            
        }
        return results;
    }
    
    /**
     * Checks if this database is available
     * @return true if this database is avaialble, currently always returns true
     */

    public boolean isAvailable() {
        return true;
    }
    
    /**
     * checks if this database is online, that is based on the internet
     * @return true if the database is reliant on the internet
     */

    public boolean isOnline() {
        return isOnline;
    }
    
    /**
     * Gets the type of database
     * @return the type of database
     */

    public DatabaseConnector.DatabaseType getType() {
        return type;
    }
    
    /**
     * Gets the priority of this database. 
     * This is used to determine which database should be searched by the Database Connector
     * @return the priority of this database
     */

    public int getPriority() {
        return priority;
    }
    
    /**
     * Updates this database by loading in a new list of POIs.
     * This method clears(removes) the previous POIs, thus the new list must be complete.
     * To add additional POIs addPOI() method instead.
     * This method is used by the update to update this database based on new XML files.
     * @param pois All the new POIs which this database should contain
     */
    
    public void update(ArrayList<POI> pois)
    {
        allPOIObjects = pois;
        loadAllTypes(); 
    }

}
